<?php
/**
 * Created by PhpStorm.
 * User: Jozef Môstka
 * Date: 5/25/14
 * Time: 7:32 PM
 */

namespace com\mostka\serializer;


class BadPrimitiveTypeException extends \Exception{
    public function __construct($b1,$b2) {
        parent::__construct($b1."!=".$b2);
    }
}
class Serializer implements SerializerInt{
    private static $INT_MAX_VALUE=2147483647;
    private static $Short_MAX_VALUE=32767;
    private static $Short_MIN_VALUE=-32768;
    private static $Long_MAX_VALUE=9223372036854775807;
    private static $Long_MIN_VALUE=-9223372036854775808;
    private static $Float_MAX_VALUE=3.4028235E38;
    private static $Float_MIN_VALUE=1.4E-45;
    private static $Double_MAX_VALUE=1.7976931348623157E308;
    private static $Double_MIN_VALUE=4.9E-324;

    const
        NONE		=0x00,
        SHORT		=0x01,
        SHORT_ARR	=0x02,
        INT		    =0x03,
        INT_ARR	    =0x04,
        LONG		=0x05,
        LONG_ARR	=0x06,
        FLOAT		=0x07,
        FLOAT_ARR	=0x08,
        DOUBLE	    =0x09,
        DOUBLE_ARR  =0x10,
        BOOLEAN	    =0x11,
        BOOLEAN_ARR =0x12,
        CHAR		=0x13,
        CHAR_ARR	=0x14,
        STRING	    =0x15,
        STRING_ARR  =0x16,
        BYTE_ARR	=0x17,
        OBJECT		=0x18,
        OBJECT_ARR	=0x18;

    private $writeBuffer;
    private $readBuffer;
    private $readPossition;

    /**
     * @param String $buffer
     */
    public function __construct($buffer){
        $arr=unpack("C*",$buffer);
        foreach($arr as $javaByte){
            //$this->readBuffer[]=$javaByte>127?$javaByte-256:$javaByte;
            $this->readBuffer[]=$javaByte;
        }
        $this->readPossition=0;
    }

    public function getBuffer(){
        return pack("C*",$this->writeBuffer);
    }
    public function getByteBuffer(){
        return $this->writeBuffer;
    }
    public function getReadBuffer(){
        return $this->readBuffer;
    }

    public function writeByte($bs) {
        // TODO: Implement writeByte() method.
    }

    public function writeByteArr($b) {
        // TODO: Implement writeByteArr() method.
    }

    public function writeShort($value) {
        // TODO: Implement writeShort() method.
    }

    public function writeShortArr($value) {
        // TODO: Implement writeShortArr() method.
    }

    public function writeInteger($value) {
        // TODO: Implement writeInteger() method.
    }

    public function writeIntegerArr($value) {
        // TODO: Implement writeIntegerArr() method.
    }

    public function writeLong($value) {
        // TODO: Implement writeLong() method.
    }

    public function writeLongArr($value) {
        // TODO: Implement writeLongArr() method.
    }

    public function writeFloat($f, $makker = true) {
        // TODO: Implement writeFloat() method.
    }

    public function writeFloatArr($value) {
        // TODO: Implement writeFloatArr() method.
    }

    public function writeDouble($value) {
        // TODO: Implement writeDouble() method.
    }

    public function writeDoubleArr($value) {
        // TODO: Implement writeDoubleArr() method.
    }

    public function writeBoolean($value) {
        // TODO: Implement writeBoolean() method.
    }

    public function writeBooleanArr($value) {
        // TODO: Implement writeBooleanArr() method.
    }

    public function writeChar($value, $makker = true) {
        // TODO: Implement writeChar() method.
    }

    public function writeCharArr($value) {
        // TODO: Implement writeCharArr() method.
    }

    public function writeString($value) {
        // TODO: Implement writeString() method.
    }

    public function writeStringArr($value) {
        // TODO: Implement writeStringArr() method.
    }

    public function readByte() {
        return $this->readBuffer[$this->readPossition++];
    }

    public function checkType($type) {
        $b = $this->readByte();
        if ($b!=$type){
            throw new BadPrimitiveTypeException($b,$type);
        }
    }

    public function readByteArray() {
        $this->checkType(self::BYTE_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $bytes=array();
        for($i=0;$i<$len;$i++){
            $bytes[$i]=$this->readByte();
        }
        return $bytes;
    }

    public function readShort() {
        $this->checkType(self::SHORT);
        return
            ($this->readByte()<<8) +
            ($this->readByte());
    }

    public function readShortArr() {
        $this->checkType(self::SHORT_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $bytes=array();
        for($i=0;$i<$len;$i++){
            $bytes[$i]=
                ($this->readByte()<<8) +
                ($this->readByte());
        }
        return $bytes;
    }

    public function readInteger($maker=true) {
        if ($maker) $this->checkType(self::INT);
        $ret =
            $this->readByte() & 0xFF |
            ($this->readByte() & 0xFF) << 8 |
            ($this->readByte() & 0xFF) << 16 |
            ($this->readByte() & 0xFF) << 24;
        if ($ret>self::$INT_MAX_VALUE){
            return -($ret-self::$INT_MAX_VALUE*2);
        }
        return $ret;

    }

    public function readIntegerArr() {
        $this->checkType(self::INT_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $bytes=array();
        for($i=0;$i<$len;$i++){
            $byte =
                $this->readByte() & 0xFF |
                ($this->readByte() & 0xFF) << 8 |
                ($this->readByte() & 0xFF) << 16 |
                ($this->readByte() & 0xFF) << 24;
            if ($byte>self::$INT_MAX_VALUE){
                $byte= -($byte-self::$INT_MAX_VALUE*2);
            }
            $bytes[$i]=$byte;
        }
        return $bytes;
    }

    public function readLong() {
        $this->checkType(self::LONG);
        return (
            $this->readByte() & 0xFF |
            ($this->readByte() & 0xFF) << 8 |
            ($this->readByte() & 0xFF) << 16 |
            ($this->readByte() & 0xFF) << 24 |
            ($this->readByte() & 0xFF) << 32 |
            ($this->readByte() & 0xFF) << 40 |
            ($this->readByte() & 0xFF) << 48 |
            ($this->readByte() & 0xFF) << 56);
    }

    public function readLongArr() {
        $this->checkType(self::LONG_ARR);
        $len = $this->readInteger(false);
		if ($len==-1){
            return null;
        }
		$ret = array();
		for($i=0;$i<$len;$i++){
            $ret[$i]=(
                $this->readByte() & 0xFF |
                ($this->readByte() & 0xFF) << 8 |
                ($this->readByte() & 0xFF) << 16 |
                ($this->readByte() & 0xFF) << 24 |
                ($this->readByte() & 0xFF) << 32 |
                ($this->readByte() & 0xFF) << 40 |
                ($this->readByte() & 0xFF) << 48 |
                ($this->readByte() & 0xFF) << 56);
		}
		return $ret;
    }

    public function readFloat() {
        $this->checkType(self::FLOAT);
		return (
            ($this->readByte()<<16) +
            ($this->readByte()<<8) +
            ($this->readByte()));
    }

    public function readFloatArr() {
        $this->checkType(self::FLOAT_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $ret = array();
        for($i=0;$i<$len;$i++){
            $ret[$i]=(
                ($this->readByte()<<16) +
                ($this->readByte()<<8) +
                ($this->readByte()));
        }
        return $ret;
    }

    public function readDouble($checkMaker=true) {
        if ($checkMaker)$this->checkType(self::DOUBLE);
        $str=
            chr($this->readByte()).
            chr($this->readByte()).
            chr($this->readByte()).
            chr($this->readByte()).
            chr($this->readByte()).
            chr($this->readByte()).
            chr($this->readByte()).
            chr($this->readByte());
        $d=unpack('d',$str);
        return $d[1];
    }

    public function readDoubleArr() {
        $this->checkType(self::DOUBLE_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $ret = array();
        for($i=0;$i<$len;$i++){
            $ret[$i]=$this->readDouble(false);
        }
        return $ret;
    }

    public function readBoolean() {
        $this->checkType(self::BOOLEAN);
        return $this->readByte()==1;
    }

    public function readBooleanArr() {
        $this->checkType(self::BOOLEAN_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $ret = array();
        for($i=0;$i<$len;$i++){
            $ret[$i]=$this->readByte()==1;
        }
        return $ret;
    }

    public function readChar() {
        $this->checkType(self::CHAR);
        return (
            ($this->readByte()<<8) +
            ($this->readByte()));
    }

    public function readCharArr() {
        $this->checkType(self::CHAR_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $ret = array();
        for($i=0;$i<$len;$i++){
            $ret[$i]=(
                ($this->readByte()<<8) +
                ($this->readByte()));
        }
        return $ret;
    }

    public function readString($checkMaker=true) {
        if ($checkMaker) $this->checkType(self::STRING);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $ret = "";
        for($i=0;$i<$len;$i++){
            $ret.=chr($this->readByte());
        }
        return $ret;
    }

    public function readStringArr() {
        $this->checkType(self::STRING_ARR);
        $len = $this->readInteger(false);
        if ($len==-1){
            return null;
        }
        $ret = array();
        for($i=0;$i<$len;$i++){
            $ret[$i]=$this->readString(false);
        }
        return $ret;
    }
}